package com.xjc.agent;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.LoaderClassPath;
import javassist.bytecode.*;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;

/**
 * @author: 谢君臣
 * @Date: 2022/10/30-13:38
 * @version: 1.0
 * @function: com.xjc.agent.MainAgent
 */
public class MainAgent {

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("agentArgs : " + agentArgs);
        //加入自定义转换器
        inst.addTransformer(new MyTransformer(), true);
    }

}
//通过ClassFileTransformer接口，可以在类加载之前，重写字节码
class MyTransformer implements ClassFileTransformer {

    /**
     * 参数：
     * loader - 定义要转换的类加载器；如果是引导加载器，则为 null
     * className - 完全限定类内部形式的类名称和 The Java Virtual Machine Specification 中定义的接口名称。例如，"java/util/List"。
     * classBeingRedefined - 如果是被重定义或重转换触发，则为重定义或重转换的类；如果是类加载，则为 null
     * protectionDomain - 要定义或重定义的类的保护域
     * classfileBuffer - 类文件格式的输入字节缓冲区（不得修改）
     * 返回：
     * 一个格式良好的类文件缓冲区（转换的结果），如果未执行转换,则返回 null。
     * 抛出：
     * IllegalClassFormatException - 如果输入不表示一个格式良好的类文件
     */
    @Override
    public byte[] transform (ClassLoader loader, String className, Class<?> classBeingRedefined,
                             ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

        // 对自定义的类进行 com/xjc/demo/controller 下的类进行修改
        if (!className.equals("com/example/demo/bean/Person")) {
            return null;
        }
        System.out.println(className);
        System.out.println(className.startsWith("com/example/demo/bean"));
        try {
            //借助JavaAssist工具，进行字节码插桩
            String clazzName = className.replace("/",".");
            System.out.println(clazzName);
            System.out.println(ClassPool.getDefault());
            ClassPool pool = ClassPool.getDefault();
            if(pool==null) {
                System.out.println("pool is null!");
            }
            System.out.println(pool);
            pool.appendClassPath(new LoaderClassPath(Thread.currentThread().getContextClassLoader()));
            CtClass clazz = pool.get(clazzName);
            String methodName = "test";
            CtMethod personFly = clazz.getDeclaredMethod(methodName);
            // 判断方法是不是静态
            MethodInfo methodInfo = personFly.getMethodInfo();
            boolean isStatic = (methodInfo.getAccessFlags() & AccessFlag.STATIC) != 0;
            CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
            LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute.getAttribute(LocalVariableAttribute.tag);
            CtClass[] parameterTypes = personFly.getParameterTypes();

            CtClass returnType = personFly.getReturnType();
            String returnTypeName = returnType.getName();

            System.out.println("类名：" + clazzName);
            System.out.println("方法：" + methodName);
            System.out.println("类型：" + (isStatic ? "静态方法" : "非静态方法"));
            System.out.println("描述：" + methodInfo.getDescriptor());
            System.out.println("入参[名称]：" + attr.variableName(1) + "，" + attr.variableName(2));
            System.out.println("入参[类型]：" + parameterTypes[0].getName() + "，" + parameterTypes[1].getName());
            System.out.println("出参[类型]：" + returnTypeName);

            //在目标方法前后，插入代码
            personFly.insertBefore("System.out.println(\"入参 "+ attr.variableName(1)+" = \"+"+ attr.variableName(1) +");");
            personFly.insertBefore("System.out.println(\"入参 "+ attr.variableName(2)+" = \"+"+ attr.variableName(2) +");");
            personFly.insertAfter("System.out.println(\"--- after toString ---\");");

            byte[] bytecode = clazz.toBytecode();
            clazz.writeFile("Person_test");

            return bytecode;

        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("error");
        }
        return null;
    }
}
